import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import axios from 'axios'
import * as crypto from 'crypto'
import * as xml2js from 'xml2js'
import {
  PaymentRequest,
  PaymentResponse,
  RefundRequest,
  RefundResponse,
} from '../interfaces/payment.interface'
import { PaymentProvider } from '../interfaces/payment.provider'

interface WechatConfig {
  payment: {
    appId: string
    mchId: string
    apiKey: string
    notifyUrl: string
    refundNotifyUrl: string
    tradeType: string
    timeExpire: number
    sandbox: boolean
    certPath: string
  }
}

@Injectable()
export class WechatPayService implements PaymentProvider {
  private readonly config: WechatConfig

  constructor(private readonly configService: ConfigService) {
    this.config = {
      payment: {
        appId: this.configService.get('WECHAT_APP_ID'),
        mchId: this.configService.get('WECHAT_MCH_ID'),
        apiKey: this.configService.get('WECHAT_API_KEY'),
        notifyUrl: this.configService.get('WECHAT_NOTIFY_URL'),
        refundNotifyUrl: this.configService.get('WECHAT_REFUND_NOTIFY_URL'),
        tradeType: 'JSAPI',
        timeExpire: 7200,
        sandbox: this.configService.get('WECHAT_SANDBOX') === 'true',
        certPath: this.configService.get('WECHAT_CERT_PATH'),
      },
    }
  }

  private getNonceStr(): string {
    return Math.random().toString(36).substr(2, 15)
  }

  private generateSign(params: Record<string, string>): string {
    const sortedParams = Object.keys(params)
      .sort()
      .map((key) => `${key}=${params[key]}`)
      .join('&')

    const stringSignTemp = `${sortedParams}&key=${this.config.payment.apiKey}`
    return crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase()
  }

  async createPayment(paymentRequest: PaymentRequest): Promise<PaymentResponse> {
    const { payment } = this.config
    const nonceStr = this.getNonceStr()
    const timeStamp = Math.floor(Date.now() / 1000).toString()

    const params = {
      appid: payment.appId,
      mch_id: payment.mchId,
      nonce_str: nonceStr,
      body: paymentRequest.description,
      out_trade_no: paymentRequest.orderId,
      total_fee: paymentRequest.amount.toString(),
      spbill_create_ip: paymentRequest.clientIp,
      notify_url: payment.notifyUrl,
      trade_type: payment.tradeType,
      openid: paymentRequest.userId,
      time_start: new Date().toISOString().replace(/[-:]/g, '').split('.')[0],
      time_expire: new Date(Date.now() + payment.timeExpire * 1000)
        .toISOString()
        .replace(/[-:]/g, '')
        .split('.')[0],
    }

    const sign = this.generateSign(params)
    const xml = this.buildXml({ ...params, sign })

    try {
      const response = await axios.post(
        payment.sandbox
          ? 'https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder'
          : 'https://api.mch.weixin.qq.com/pay/unifiedorder',
        xml,
        {
          headers: { 'Content-Type': 'text/xml' },
        }
      )

      const result = await this.parseXml(response.data)
      if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS') {
        return {
          paymentId: result.prepay_id,
          paymentParams: {
            appId: payment.appId,
            timeStamp,
            nonceStr,
            package: `prepay_id=${result.prepay_id}`,
            signType: 'MD5',
            paySign: this.generateSign({
              appId: payment.appId,
              timeStamp,
              nonceStr,
              package: `prepay_id=${result.prepay_id}`,
              signType: 'MD5',
            }),
          },
        }
      }
      throw new Error(result.return_msg || result.err_code_des)
    } catch (error) {
      throw new Error(`微信支付创建失败: ${error.message}`)
    }
  }

  async refund(refundRequest: RefundRequest): Promise<RefundResponse> {
    const { payment } = this.config
    const nonceStr = this.getNonceStr()

    const params = {
      appid: payment.appId,
      mch_id: payment.mchId,
      nonce_str: nonceStr,
      out_trade_no: refundRequest.orderId,
      out_refund_no: refundRequest.refundId,
      total_fee: refundRequest.totalAmount.toString(),
      refund_fee: refundRequest.refundAmount.toString(),
      notify_url: payment.refundNotifyUrl,
    }

    const sign = this.generateSign(params)
    const xml = this.buildXml({ ...params, sign })

    try {
      const response = await axios.post(
        payment.sandbox
          ? 'https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/refund'
          : 'https://api.mch.weixin.qq.com/secapi/pay/refund',
        xml,
        {
          headers: { 'Content-Type': 'text/xml' },
          httpsAgent: this.getHttpsAgent(),
        }
      )

      const result = await this.parseXml(response.data)
      if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS') {
        return {
          refundId: result.refund_id,
          status: 'SUCCESS',
        }
      }
      throw new Error(result.return_msg || result.err_code_des)
    } catch (error) {
      throw new Error(`微信退款失败: ${error.message}`)
    }
  }

  private buildXml(params: Record<string, string>): string {
    const builder = new xml2js.Builder({
      headless: true,
      renderOpts: { pretty: false },
    })
    return builder.buildObject({ xml: params })
  }

  private async parseXml(xml: string): Promise<any> {
    return new Promise((resolve, reject) => {
      xml2js.parseString(xml, { explicitArray: false }, (err, result) => {
        if (err) return reject(err)
        resolve(result.xml)
      })
    })
  }

  private getHttpsAgent() {
    const fs = require('fs')
    const path = require('path')
    const https = require('https')

    const certPath = path.resolve(process.cwd(), this.config.payment.certPath)
    return new https.Agent({
      pfx: fs.readFileSync(path.join(certPath, 'apiclient_cert.p12')),
      passphrase: this.config.payment.mchId,
    })
  }
}
